﻿using EasyNet.Componets.Core.Expressions;
using EasyNet.Componets.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace EasyNet.Componets.Core
{
    /// <summary>
    /// Represents the extender for Expression[Func[T, bool]] type.
    /// This is part of the solution which solves
    /// the expression parameter problem when going to Entity Framework by using
    /// Apworks specifications. For more information about this solution please
    /// refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx.
    /// </summary>
    public static class LambdaBuilder
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }

        #region Private Methods
        private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            if (second == null)
            {
                return first;
            }
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Combines two given expressions by using the AND semantics.
        /// </summary>
        /// <typeparam name="T">The type of the object.</typeparam>
        /// <param name="first">The first part of the expression.</param>
        /// <param name="second">The second part of the expression.</param>
        /// <returns>The combined expression.</returns>
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> first)
        {
            return first;
        }

        /// <summary>
        /// Combines two given expressions by using the AND semantics.
        /// </summary>
        /// <typeparam name="T">The type of the object.</typeparam>
        /// <param name="first">The first part of the expression.</param>
        /// <param name="second">The second part of the expression.</param>
        /// <returns>The combined expression.</returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }
        /// <summary>
        /// Combines two given expressions by using the OR semantics.
        /// </summary>
        /// <typeparam name="T">The type of the object.</typeparam>
        /// <param name="first">The first part of the expression.</param>
        /// <param name="second">The second part of the expression.</param>
        /// <returns>The combined expression.</returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }
        #endregion

        #region Operate Methods

        /// <summary>
        /// 创建lambda表达式：p=>p.propertyName
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="sort"></param>
        /// <returns></returns>
        public static Expression<Func<T, TKey>> CreatePropertyExpression<T, TKey>(PropertyInfo property, string parameterName, string[] fieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, fieldArray, property, out memberType);
            return Expression.Lambda<Func<T, TKey>>(member, parameter);
        }

        public static Expression<Func<T, bool>> Equal<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            //创建常量表达式
            ConstantExpression constantValue = CreateConstantExpression(value, memberType);

            BinaryExpression body = Expression.Equal(member, constantValue);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
            return lambda;
        }


        public static Expression<Func<T, bool>> NotEqual<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            //创建常量表达式
            ConstantExpression constantValue = CreateConstantExpression(value, memberType);

            BinaryExpression body = Expression.NotEqual(member, constantValue);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> LessThan<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            //创建常量表达式
            ConstantExpression constantValue = CreateConstantExpression(value, memberType);

            BinaryExpression body = Expression.LessThan(member, constantValue);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> LessThanOrEqual<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            //创建常量表达式
            ConstantExpression constantValue = CreateConstantExpression(value, memberType);

            BinaryExpression body = Expression.LessThanOrEqual(member, constantValue);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> GreaterThan<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            //创建常量表达式
            ConstantExpression constantValue = CreateConstantExpression(value, memberType);

            BinaryExpression body = Expression.GreaterThan(member, constantValue);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> GreaterThanOrEqual<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            //创建常量表达式
            ConstantExpression constantValue = CreateConstantExpression(value, memberType);

            BinaryExpression body = Expression.GreaterThanOrEqual(member, constantValue);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> Contains<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);

            MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            ConstantExpression constant = Expression.Constant(value, typeof(string));

            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(Expression.Call(member, method, constant), parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> NotContains<T>(PropertyInfo property, string parameterName, object value, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);
            MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            ConstantExpression constant = Expression.Constant(value, typeof(string));

            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(Expression.Not(Expression.Call(member, method, constant)), parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> InCollection<T>(PropertyInfo property, string parameterName, List<string> valueArray, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);

            Type genericType = typeof(List<>);
            genericType = genericType.MakeGenericType(memberType);

            var newListExpression = CreateListExpression(valueArray, memberType, genericType);

            MethodInfo method = genericType.GetMethod("Contains");

            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(Expression.Call(newListExpression, method, member), parameter);
            return lambda;
        }

        public static Expression<Func<T, bool>> NotInCollection<T>(PropertyInfo property, string parameterName, List<string> valueArray, string[] conditionFieldArray)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), parameterName);
            Type memberType = null;
            MemberExpression member = CreateMemberExpress(parameter, conditionFieldArray, property, out memberType);

            Type genericType = typeof(List<>);
            genericType = genericType.MakeGenericType(memberType);

            var newListExpression = CreateListExpression(valueArray, memberType, genericType);

            MethodInfo method = genericType.GetMethod("Contains");

            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(Expression.Not(Expression.Call(newListExpression, method, member)), parameter);
            return lambda;
        }

        private static ListInitExpression CreateListExpression(List<string> valueArray, Type memberType, Type genericType)
        {
            System.Linq.Expressions.NewExpression newListExpression = System.Linq.Expressions.Expression.New(genericType);

            var constantValueList = new List<ConstantExpression>();
            foreach (var item in valueArray)
            {
                var typeValue = Convert.ChangeType(item, memberType);
                ConstantExpression constantValue = Expression.Constant(typeValue);
                constantValueList.Add(constantValue);
            }
            ListInitExpression listInitExpression = Expression.ListInit(newListExpression, constantValueList);
            return listInitExpression;
        }

        private static ConstantExpression CreateConstantExpression(object value, Type type)
        {

            var typeValue = Convert.ChangeType(value, type);
            ConstantExpression constantValue = Expression.Constant(typeValue);

            //ConstantExpression constantValue = null;
            //if (type.IsValueType || type == typeof(string))
            //{
            //    var typeValue = Convert.ChangeType(value, type);
            //    constantValue = Expression.Constant(typeValue);
            //}
            //else if (type.IsGenericType || type.IsClass
            //       || type.GetGenericTypeDefinition() == typeof(IList<>)
            //       || type.IsArray
            //       )
            //{

            //}


            return constantValue;
        }

        private static MemberExpression CreateMemberExpress(ParameterExpression parameter, string[] conditionFieldArray, PropertyInfo property, out Type memberType)
        {
            memberType = property.PropertyType;
            MemberExpression member = Expression.Property(parameter, property);
            if (conditionFieldArray.Length > 1)
            {
                for (var i = 1; i < conditionFieldArray.Length; i++)
                {
                    var subProperties = memberType.GetPropertyInfoWithCache();
                    var field = conditionFieldArray[i].ToLower().Trim();
                    var subProperty = subProperties.First(f => f.Name.ToLower() == field);
                    memberType = subProperty.PropertyType;

                    member = Expression.Property(member, subProperty);
                }
            }

            return member;
        }
        #endregion
    }
}
